/* LCD画点、画线、画矩形操作 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <linux/fb.h>

#define argb8888_to_rgb565(color) ({        \
            unsigned int temp = (color);    \
            ((temp & 0xF80000UL) >> 8) |    \
            ((temp & 0xFC00UL) >> 5) |      \
            ((temp & 0xF8UL) >> 3);         \
        })

static int width; /* LCD x分辨率 */
static int height; /* LCD y分辨率 */
static unsigned short *screen_base = NULL; /* 映射后的显存基地址 */

/**
 * @brief 画点
 * 
 * @param x 
 * @param y 
 * @param color 
 * @return
 */
static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color); /* 得到RGB565颜色值 */
    /* 对传入参数的校验 */
    if (x >= width) {
        x = width - 1;
    }
    if (y >= height) {
        y = height - 1;
    }
    /* 填充颜色 */
    screen_base[y * width + x] = rgb565_color;
}

/**
 * @brief 画线
 * @param x 
 * @param y 
 * @param dir 
 * @param length 
 * @param color 
 * @return
 */
static void lcd_draw_line(unsigned int x, unsigned int y, int dir, 
                          unsigned int length, unsigned int color)
{
    /* 得到 RGB565颜色值 */
    unsigned short rgb565_color = argb8888_to_rgb565(color);
    unsigned int end;
    unsigned long temp;
    /* 对传入参数的校验 */
    if (x >= width) {
        x = width - 1;
    }
    if (y >= height) {
        y = height - 1;
    }
    /* 填充颜色 */
    temp = y * width + x; /* 定位到起点 */
    if (dir) { /* 水平线 */
        end = x + length - 1;
        if (end >= width) {
            end = width - 1;
        }
        for ( ; x <= end; x++, temp++) {
            screen_base[temp] = rgb565_color;
        }
    } else { /* 垂直线 */
        end = y + length - 1;
        if (end >= height) {
            end = height - 1;
        }
        for ( ; y <= end; y++, temp += width) {
            screen_base[temp] = rgb565_color;
        }
    }
}

/**
 * @brief 画矩形
 * 
 * @param start_x 
 * @param end_x 
 * @param start_y 
 * @param end_y 
 * @param color 
 */
static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,
                               unsigned int start_y, unsigned int end_y,
                               unsigned int color)
{
    int x_len = end_x - start_x + 1;
    int y_len = end_y - start_y - 1;

    lcd_draw_line(start_x, start_y, 1, x_len, color); /* 上边 */
    lcd_draw_line(start_x, end_y, 1, x_len, color); /* 下边 */
    lcd_draw_line(start_x, start_y + 1, 0, y_len, color); /* 左边 */
    lcd_draw_line(end_x, start_y + 1, 0, y_len, color); /* 右边 */
}

/**
 * @brief 将一个矩形区域填充为参数 color所指定的颜色
 * 
 * @param start_x 
 * @param end_x 
 * @param start_y 
 * @param end_y 
 * @param color 
 */
static void lcd_fill(unsigned int start_x, unsigned int end_x,
                     unsigned int start_y, unsigned int end_y, 
                     unsigned int color)
{
    /* 得到 RGB565颜色值 */
    unsigned short rgb565_color = argb8888_to_rgb565(color);
    unsigned long temp;

    /* 对传入参数的校验 */
    if (end_x >= width) { end_x = width - 1; }
    if (end_y >= height) { end_y = height - 1; }
    /* 填充颜色 */
    temp = start_y * width; /* 定位到起点行首 */
    for ( ; start_y <= end_y; start_y++, temp += width) {
        for (unsigned int x = start_x; x <= end_x; x++) {
            screen_base[temp + x] = rgb565_color;
        }
    }
}

/**
 * @brief 
 * 
 * @param argc 
 * @param argv 
 * @return int 
 */
int main(int argc, char const *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd, ret;

    /* 打开 framebuffer设备 */
    fd = open("/dev/fb0", O_RDWR);
    if (0 > fd) {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;
    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }
    /* 画正方形方块 */
    int w = height * 0.25; /* 方块的宽度为 1/4屏幕高度 */
    lcd_fill(0, width - 1, 0, height - 1, 0x0); /* 清屏（屏幕显示黑色） */
    lcd_fill(0, w, 0, w, 0xFF0000); /* 红色方块 */
    lcd_fill(width - w, width - 1, 0, w, 0xFF00); /* 绿色方块 */
    lcd_fill(0, w, height - w, height - 1, 0xFF); /* 蓝色方块 */
    lcd_fill(width - w, width - 1, height - w, height - 1, 0xFFFF00); /* 黄色方块 */
    /* 画线：十字交叉线 */
    lcd_draw_line(0, height * 0.5, 1, width, 0xFFFFFF); /* 白色线 */
    lcd_draw_line(width * 0.5, 0, 0, height, 0xFFFFFF); /* 白色线 */
    /* 画矩形 */
    unsigned int start_x, start_y, end_x, end_y;
    start_x = 0.25 * width;
    start_y = w;
    end_x = width - start_x;
    end_y = height - start_y;
    for ( ; (start_x <= end_x) && (start_y <= end_y); 
         start_x += 5, start_y += 5, end_x -= 5, end_y -= 5) {
        lcd_draw_rectangle(start_x, end_x, start_y, end_y, 0xFFFFFF); 
    }
    /* 退出 */
    munmap(screen_base, screen_size); /* 取消映射 */
    close(fd); /* 关闭文件 */
    exit(EXIT_SUCCESS); /* 退出进程 */
}

